
// Copyright (C) 2009 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Derm.BindGen
{
	partial class Function
	{
		/// <summary>
		/// Function parameter.
		/// </summary>
		/// <remarks>
		/// <para>
		/// A function parameter is a mean to pass information to a called function. A function parameter is used
		/// to declare the following statements:
		/// - Import function declaration
		/// - Delegate function declaration
		/// - Implementation function signature declaration
		/// - Implementation function body variable
		/// </para>
		/// <para>
		/// A parameter is characterized by many factors. A parameter has defined:
		/// - Name: it has to be unique in the function parameters.
		/// - Type: it can be a simple type or an array of any simple type. In the case the type is
		///   an array type, a length of array could be specified as fixed or dependent on other
		///   function parameters.
		/// - Function direction: it can be input, output or a reference value (input and output).
		/// - Marshalling direction: it can be marshalled as input, output or in both direction. This is determinant
		///   since parameters are passed to unmanaged code.
		/// </para>
		/// </remarks>
		public class Parameter
		{
			#region Constructors

			/// <summary>
			/// Parameter constructor.
			/// </summary>
			/// <param name="name">
			/// A <see cref="System.String"/> specifying parameter name.
			/// </param>
			public Parameter(string name)
			{
				Debug.Assert(name != null);

				// Store name, leave other to default values
				mName = name;
			}

			/// <summary>
			/// Parameter copy constructor.
			/// </summary>
			/// <param name="p">
			/// A <see cref="Parameter"/> to be cloned.
			/// </param>
			private Parameter(Parameter p)
			{
				mName = p.mName;					// Copy parameter name
				mSpecTypeName = p.mSpecTypeName;	// Copy parameter type (spec)
				mTypeName = p.mTypeName;			// Copy parameter type (spec mapped)
			}

			#endregion

			#region Parameter Name

			/// <summary>
			/// Fixed pointer variable prefix.
			/// </summary>
			public const string FP_PREFIX = "fp_";
			/// <summary>
			/// Pinned pointer variable prefix.
			/// </summary>
			public const string PP_PREFIX = "pp_";

			/// <summary>
			/// Parameter name.
			/// </summary>
			public string Name
			{
				get {
					if (SpecParser.IsCsKeywork(mName) == true)
						return ("@"+mName);
					else
						return (mName);
				}
			}

			/// <summary>
			/// Parameter name with prefix.
			/// </summary>
			public string GetName(string prefix)
			{
				string pName = prefix+mName;

				if (SpecParser.IsCsKeywork(pName) == true)
					return ("@"+pName);
				else
					return (pName);
			}

			/// <summary>
			/// Parameter name used in delegate declaration.
			/// </summary>
			public string DelegateName
			{
				get {
					// Parameter direction
					if (MDirection == MarshalDirection.Out)
						return ("[Out] " + DelegateTypeName + " " + Name);
					else
						return (DelegateTypeName + " " + Name);
				}
			}

			/// <summary>
			/// Parameter name used in delegate call.
			/// </summary>
			public string DelegateCallName
			{
				get {
					if (Indirection == IndirectionMode.ByValue) {
						if (FDirection == FuncDirection.Out)
							return (GetName(FP_PREFIX));
						else
							return (Name);
					} else if (Indirection == IndirectionMode.ByArray) {
						if (IsPinned == true)
							return (GetName(PP_PREFIX)+".AddrOfPinnedObject()");
						else if (IsSafe == false)
							return (GetName(FP_PREFIX));
						else
							return (Name);
					} else {
						return (Name);
					}
				}
			}

			/// <summary>
			/// Parameter name.
			/// </summary>
			private string mName = null;

			#endregion

			#region Parameter Type

			/// <summary>
			/// Parameter type used during function import definition.
			/// </summary>
			public string ImportTypeName
			{
				get {
					string iType;

					switch (Indirection) {
					case IndirectionMode.ByArray:
						iType = SpecParser.GetCsUnsafeArrayType(mTypeName);
						if ((iType == "string") && (MDirection == MarshalDirection.Out))
							iType = "StringBuilder";
						break;
					default:
						iType = SpecParser.GetCsUnsafeType(mTypeName);
						break;
					}

					return (iType);
				}
			}

			/// <summary>
			/// Parameter type used during function delegate definition.
			/// </summary>
			public string DelegateTypeName
			{
				get {
					string pType;

					switch (Indirection) {
					case IndirectionMode.ByArray:
						pType = SpecParser.GetCsUnsafeArrayType(mTypeName);
						if ((pType == "string") && (MDirection == MarshalDirection.Out))
							pType = "StringBuilder";
						break;
					default:
						pType = SpecParser.GetCsUnsafeType(mTypeName);
						break;
					}

					switch (FDirection) {
					case FuncDirection.Out:
						return (pType+"*");
					default:
						return (pType);
					}
				}
			}

			/// <summary>
			/// Parameter type used using function signature definition.
			/// </summary>
			public string ImplementationTypeName
			{
				get {
					string pType;

					switch (Indirection) {
						case IndirectionMode.ByArray:
							pType = SpecParser.GetCsSafeArrayType(mTypeName);
							if ((pType == "string") && (MDirection == MarshalDirection.Out))
								pType = "StringBuilder";
							break;
						case IndirectionMode.ByPointer:
							pType = SpecParser.GetCsUnsafeArrayType(mTypeName);
							break;
						default:
							pType = SpecParser.GetCsSafeType(mTypeName);
							break;
					}

					switch (FDirection) {
						case FuncDirection.Out:
							return ("out "+pType);
						case FuncDirection.Ref:
							return ("ref "+pType);
						default:
							return (pType);
					}
				}
			}

			/// <summary>
			/// Parameter type used in function specification, mapped with relative type map.
			/// </summary>
			public string SpecMappedTypeName
			{
				get { return (mTypeName); }
			}

			/// <summary>
			/// Parameter type used in function specification, mapped with relative type map.
			/// </summary>
			public string SpecTypeName
			{
				get { return (mSpecTypeName); }
			}

			/// <summary>
			/// Parameter type name.
			/// </summary>
			public void SetTypeName(string sType, string sMapType)
			{
				mSpecTypeName = sType;
				mTypeName = sMapType;
			}

			/// <summary>
			/// Parameter type, using specification tipe mapping.
			/// </summary>
			public string mTypeName = null;
			/// <summary>
			/// Parameter type, as used in specification.
			/// </summary>
			public string mSpecTypeName = null;

			#endregion

			#region Parameter Passing

			/// <summary>
			/// Parameter direction for being marshaled.
			/// </summary>
			public enum MarshalDirection
			{
				/// <summary>
				/// Input parameter.
				/// </summary>
				In,
				/// <summary>
				/// Output parameter.
				/// </summary>
				Out,
				/// <summary>
				/// Input & Output parameter.
				/// </summary>
				InOut
			}

			/// <summary>
			/// Parameter direction.
			/// </summary>
			public enum FuncDirection
			{
				/// <summary>
				/// Input parameter.
				/// </summary>
				In,
				/// <summary>
				/// Output parameter.
				/// </summary>
				Out,
				/// <summary>
				/// Reference parameter.
				/// </summary>
				Ref
			}

			/// <summary>
			/// Parameter indirection.
			/// </summary>
			public enum IndirectionMode
			{
				ByValue,
				ByArray,
				ByReference,
				/// <summary>
				/// Special value (not related with OpenGL specification files) for handling pointer parameters
				/// </summary>
				ByPointer
			}

			/// <summary>
			/// Array parameter length mode.
			/// </summary>
			public enum ArraySizeMode
			{
				/// <summary>
				/// Undefinite array size.
				/// </summary>
				Undef,
				/// <summary>
				/// Fixed array size.
				/// </summary>
				Fixed,
				/// <summary>
				/// Slaved to size parameter.
				/// </summary>
				/// <remarks>
				/// The parameter name shall be "size", and it must typed GLsizei or SizeI.
				/// </remarks>
				SizeParam,
				/// <summary>
				/// Slaved to pname parameter.
				/// </summary>
				/// <remarks>
				/// The parameter name shall be "pname", and it must typed as GLenum.
				/// </remarks>
				PNameParam,
			}

			/// <summary>
			/// Parameter direction for begin marshaled.
			/// </summary>
			public MarshalDirection MDirection = MarshalDirection.In;

			/// <summary>
			/// Parameter direction.
			/// </summary>
			public FuncDirection FDirection = FuncDirection.In;

			/// <summary>
			/// Parameter value passing type.
			/// </summary>
			public IndirectionMode Indirection = IndirectionMode.ByValue;

			/// <summary>
			/// 
			/// </summary>
			/// <param name="exp">
			/// A <see cref="System.String"/> specifying the specification comment which defines
			/// the array length expression.
			/// </param>
			public void SetArraySize(string exp)
			{
				// Formality checks
				if ((exp.StartsWith("[") == false) || (exp.EndsWith("]") == false))
					throw new FormatException("array size expression \""+exp+"\" is not included in square brackets");

				// Store definition string
				mArraySizeModeString = exp;
			}

			/// <summary>
			/// 
			/// </summary>
			/// <param name="func">
			/// A <see cref="Function"/> specifying current parameter function.
			/// </param>
			/// <param name="exp">
			/// A <see cref="System.String"/> array parameter size (comment from OpenGL specs).
			/// </param>
			public void ParseArraySize(Function func)
			{
				string aSize = null;

				// No array size string?
				if (mArraySizeModeString == null)
					return;

				Debug.Assert(Indirection == IndirectionMode.ByArray);

				if (mArraySizeModeString.StartsWith("[COMPSIZE(") == true)
					aSize = mArraySizeModeString.Substring("[COMPSIZE(".Length, mArraySizeModeString.Length - "[COMPSIZE(".Length - ")]".Length);
				else
					aSize = mArraySizeModeString.Substring("[".Length, mArraySizeModeString.Length - "[".Length-"]".Length);

				// Check fixed sizes
				try {
					// Declare fixed components count
					mArrayFixedSize = Int32.Parse(aSize);
					mArraySizeMode = ArraySizeMode.Fixed;
					
					// Arrays of fixed length of 1 are threaded as out variables (by value)
					if (mArrayFixedSize == 1) {
						Indirection = IndirectionMode.ByValue;
						FDirection = FuncDirection.Out;
					}

					return;
				} catch (FormatException) { }

				// Check parameter dependencies
				foreach (Function.Parameter p in func.GetParameters()) {
					if (p.Name == aSize) {
						if ((p.SpecTypeName == "GetPName") || (p.SpecTypeName == "GLenum")) {
							CreateUnaryArrayParameterAlternative();
							return;
						} else if ((p.SpecTypeName == "SizeI") || (p.SpecTypeName == "GetTextureParameter")) {
							string pFunctName = func.ImplementationName;
							string iFynctParamType = ImplementationTypeName;

							if ((iFynctParamType != "string") && (iFynctParamType != "StringBuilder") && (iFynctParamType != "object")) {
								if (pFunctName.StartsWith("Gen") == true)
									CreateUnaryArrayParameterAlternative();
								else if (pFunctName.StartsWith("Get") == true)
									CreateUnaryArrayParameterAlternative();
							}
							return;
						}
					}
				}

				// Undefined array size
				mArraySizeMode = ArraySizeMode.Undef;
			}

			private void CreateUnaryArrayParameterAlternative()
			{
				Parameter pAlternative = new Parameter(this);

				// Array of length dependent from GetPName, add an alternative
				// for array of length 1 (out variable)

				pAlternative.Indirection = IndirectionMode.ByValue;
				pAlternative.FDirection = FuncDirection.Out;

				mAlternatives.Add(pAlternative);
			}

			/// <summary>
			/// Allowed array size.
			/// </summary>
			public ArraySizeMode ArraySize
			{
				get { return (mArraySizeMode); }
			}

			public int GetFixedArraySize()
			{
				Debug.Assert(mArraySizeMode == ArraySizeMode.Fixed);
				return (mArrayFixedSize);
			}

			/// <summary>
			/// Description of array size mode. For deferred determination.
			/// </summary>
			private string mArraySizeModeString = null;
			/// <summary>
			/// Array size mode.
			/// </summary>
			private ArraySizeMode mArraySizeMode = ArraySizeMode.Undef;
			/// <summary>
			/// Fixed array size.
			/// </summary>
			private int mArrayFixedSize = 0;

			#endregion

			#region Parameter Data Access

			/// <summary>
			/// Check whether this Parameter is safe.
			/// </summary>
			public bool IsSafe
			{
				get {
					string sType;

					switch (Indirection) {
						case IndirectionMode.ByValue:
							sType = SpecParser.GetCsUnsafeType(SpecMappedTypeName);

							if (FDirection == FuncDirection.Out) {
								if ((sType != "string") && (sType != "string[]"))
									return (false);
							}
							break;
						case IndirectionMode.ByArray:
							sType = SpecParser.GetCsUnsafeArrayType(SpecMappedTypeName);

							if ((sType != "string") && (sType != "string[]") && (sType != "IntPtr"))
								return (false);
							break;
						case IndirectionMode.ByPointer:
							return (false);
					}

					return (true);
				}
			}

			/// <summary>
			/// Check whether this Parameter must be pinned.
			/// </summary>
			public bool IsPinned
			{
				get {
					// IntPtr types shall be pinned
					if ((Indirection == IndirectionMode.ByArray) && (ImplementationTypeName == "object"))
						return (true);

					return (false);
				}
			}

			#endregion

			#region Parameter Declarations

			/// <summary>
			/// Get fixed parameter pointer declaration.
			/// </summary>
			/// <returns>
			/// It returns parameter fixed pointer variable declaration.
			/// </returns>
			public string GetFixedPointer()
			{
				Debug.Assert(IsSafe == false);
				Debug.Assert(IsPinned == false);

				switch (Indirection) {
				case IndirectionMode.ByValue:
					return ("fixed ("+ DelegateTypeName + " "+GetName(Parameter.FP_PREFIX)+" = &"+Name+")");
				case IndirectionMode.ByArray:
					return ("fixed ("+ DelegateTypeName + " "+GetName(Parameter.FP_PREFIX)+" = "+Name+")");
				}

				return (null);
			}

			/// <summary>
			/// Get pinned parameter pointer declaration.
			/// </summary>
			/// <returns>
			/// It returns parameter pinned pointer variable declaration.
			/// </returns>
			public string GetPinnedPointer()
			{
				Debug.Assert(IsPinned == true);

				return ("GCHandle "+GetName(Parameter.PP_PREFIX)+" = GCHandle.Alloc("+Name+", GCHandleType.Pinned);");
			}

			#endregion

			#region Parameter Alternatives

			/// <summary>
			/// Determine whether function allow a set of parameter alternatives.
			/// </summary>
			public void CheckForAlternatives(SpecParser specParser)
			{
				if (Indirection == IndirectionMode.ByArray) {
					if (
						(mSpecTypeName == "Float32") || (mSpecTypeName == "Float64") || (mSpecTypeName == "UInt32") || (mSpecTypeName == "Int32") ||
						(mSpecTypeName == "ConstVoid")
						)
					{
						Parameter paramAlternative = new Parameter(this);
						string glTypeName = specParser.MapType(mSpecTypeName);

						paramAlternative.SetTypeName(SpecParser.GetCsUnsafeArrayType(glTypeName), glTypeName);
						paramAlternative.Indirection = IndirectionMode.ByPointer;
						paramAlternative.FDirection = FuncDirection.In;

						Alternatives.Add(paramAlternative);
					}
				}
			}

			/// <summary>
			/// Parameter overloading flag.
			/// </summary>
			/// <returns>
			/// It returns whether this parameter could be substituted by
			/// another parameter.
			/// </returns>
			public bool HasAlternatives
			{
				get { return (mAlternatives.Count > 0); }
			}

			/// <summary>
			/// Parameter alternatives (used for function overloading).
			/// </summary>
			public List<Parameter> Alternatives
			{
				get { return (mAlternatives); }
			}

			public bool CauseOverrideCollision;

			/// <summary>
			/// Parameter alternatives.
			/// </summary>
			private List<Parameter> mAlternatives = new List<Parameter>();

			#endregion
		}
	}
}
